package com.raidan.dclog.core;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Non fair class. From other project :(
 * 
 * @author raidan
 */
public class FileUtils {

	/**
	 * Загружает список файлов из каталога или из JAR-ника
	 * 
	 * @param path
	 *            название ресурса (вида <code>com/raidan/dclog/core/</code>).
	 *            Обязательно со слэшем на конце!
	 * @return Коллекцию файлов, пригодных к загрузке методом loadFromJAR
	 * @throws IOException
	 */
	public static String[] listFilesFromJAR(String path, String extenstion) throws IOException {
		URL url = FileUtils.class.getProtectionDomain().getCodeSource().getLocation();
		if (url != null && url.getPath().endsWith(".jar")) {

			ZipFile file = new ZipFile(url.getPath());
			ZipEntry entry = file.getEntry(path);

			if (entry == null) {
				throw new IOException("Unable to find '" + path + "' from JAR file " + url);
			}

			if (!entry.isDirectory()) {
				throw new IOException("Path '" + path + "' is not directory. Unable to load list.");
			}

			Collection<String> fileNames = new ArrayList<String>();

			Enumeration<? extends ZipEntry> entries = file.entries();
			while (entries.hasMoreElements()) {
				entry = entries.nextElement();
				String name = entry.getName();
				if (name.startsWith(path) && !entry.isDirectory()) {
					if (extenstion == null || name.endsWith(extenstion)) {
						fileNames.add(name);
					}
				}
			}

			return fileNames.toArray(new String[fileNames.size()]);
		} else {
			File dir = new File(url.getPath() + path);
			if (!dir.isDirectory()) {
				throw new IOException("Path '" + path + "' is not directory. Unable to load list.");
			}
			File files[] = dir.listFiles();
			Collection<String> fileNames = new ArrayList<String>();
			for (File file : files) {
				String fullPath = file.getCanonicalPath().replaceAll("\\\\", "/");
				if (extenstion == null || fullPath.endsWith(extenstion)) {
					fileNames.add(fullPath.substring(fullPath.lastIndexOf(path)));
				}
			}
			return fileNames.toArray(new String[fileNames.size()]);
		}
	}

	/**
	 * <p>
	 * Загружает данные из ресурса. Под ресурсом подразумевается файл, доступный
	 * класслоадеру.
	 * 
	 * <p>
	 * <b>Note:</b> не используйте этот метод при загрузке множества файлов!
	 * Можно получить утечки памяти.
	 * 
	 * 
	 * @param filePath
	 *            название ресурса (вида
	 *            <code>com/forward/cfg/DBloader.properties</code>).
	 * @return данные
	 * @throws IOException
	 */
	public static StringBuilder loadFromResource(String filePath) throws IOException {
		InputStream stream = ClassLoader.getSystemResourceAsStream(filePath);
		if (stream == null) {
			throw new IOException("Unable to load file '" + filePath + "' from resources.");
		}
		return loadFromStream(stream);
	}

	/**
	 * <p>
	 * Загружает данные из JAR файла -- того же, из которого был загружен класс
	 * FileUtils. Если FileUtils был поднят из списка файлов -- управление будет
	 * передано методу loadFromResource. Т.е. достаточно универсальный способ
	 * загрузить любой файл из доступных ресурсов.
	 * 
	 * <p>
	 * <b>Note:</b> не используйте этот метод при загрузке множества файлов!
	 * Можно получить утечки памяти.
	 * 
	 * 
	 * @param filePath
	 *            название ресурса (вида
	 *            <code>com/forward/cfg/DBloader.properties</code>).
	 * @return данные
	 * @throws IOException
	 */
	public static StringBuilder loadFromJAR(String filePath) throws IOException {
		URL url = FileUtils.class.getProtectionDomain().getCodeSource().getLocation();
		if (url != null && url.getPath().endsWith(".jar")) {
			try {
				ZipFile file = new ZipFile(url.getPath());
				ZipEntry entry = file.getEntry(filePath);

				if (entry == null) {
					throw new IOException("Unable to find '" + filePath + "' from JAR file " + url);
				}

				if (entry.isDirectory()) {
					throw new IOException("Path '" + filePath + "' is directory, not a file. Unable to load.");
				}

				InputStream stream = file.getInputStream(entry);
				if (stream == null) {
					throw new IOException("Unable to load file '" + filePath + "' from current JAR file " + url);
				}
				return loadFromStream(stream);
			} catch (IOException io) {
				// Well, try to load anyway from dir
				try {
					return loadFromResource(filePath);
				} catch (IOException io2) {
					// Return first exception, cause this is really JAR?
					throw io;
				}
			}
		} else {
			return loadFromResource(filePath);
		}
	}

	/**
	 * <p>
	 * Загружает данные из файла.
	 * 
	 * <p>
	 * <b>Note:</b> не используйте этот метод при загрузке множества файлов!
	 * Можно получить утечки памяти.
	 * 
	 * @param fileName
	 *            путь к файлу
	 * @return данные
	 * @throws IOException
	 */
	public static StringBuilder loadFromFile(String fileName) throws IOException {
		return loadFromStream(getStreamForLoad(fileName));
	}

	/**
	 * Возвращает поток, из которого можно читать данные.
	 * 
	 * @param fileName
	 *            путь к файлу
	 * @return поток, который смотри в файл
	 * @throws IOException
	 */
	public static InputStream getStreamForLoad(String fileName) throws IOException {
		return new BufferedInputStream(new FileInputStream(fileName));
	}

	/**
	 * <p>
	 * Загружает данные из потока.
	 * 
	 * <p>
	 * <b>Note:</b> не используйте этот метод при загрузке множества файлов!
	 * Можно получить утечки памяти.
	 * 
	 * 
	 * @param stream
	 *            поток с данными (после загрузки он будет закрыт)
	 * @return данные
	 * @throws IOException
	 */
	protected static StringBuilder loadFromStream(InputStream stream) throws IOException {
		return loadFromStream(stream, null);
	}

	protected static StringBuilder loadFromStream(InputStream stream, String encoding) throws IOException {
		if (stream == null) {
			throw new IOException("Stream is empty. Nothing to load.");
		}
		StringBuilder data = new StringBuilder();

		char[] buffer = new char[Short.MAX_VALUE];
		InputStreamReader reader = encoding == null ? new InputStreamReader(stream) : new InputStreamReader(stream,
				encoding);
		int size = 0;
		while ((size = reader.read(buffer)) > 0) {
			data.append(buffer, 0, size);
		}
		buffer = null;
		reader.close();
		stream.close();
		return data;
	}

	/**
	 * Удаление файла.
	 * 
	 * @param file
	 *            файл
	 * @return true, если удаление произведено успешно и false, если нет.
	 * @todo Придумать, как получить информацию о том, почему нельзя удалить
	 *       файл
	 */
	public static boolean removeFile(File file) {
		if (file.exists() && file.isFile()) {
			return file.delete();
		} else {
			return false;
		}
	}

	/**
	 * Удаление каталога.
	 * 
	 * @param path
	 *            String
	 * @throws IOException
	 */
	public static void removeDirectory(String path) throws IOException {
		removeDirectory(new File(path));
	}

	/**
	 * Удаление каталога
	 * 
	 * @param dir
	 *            File
	 * @throws IOException
	 */
	public static void removeDirectory(File dir) throws IOException {
		if (!dir.exists()) {
			return;
		}
		if (!dir.isDirectory()) {
			throw new IOException("Directory '" + dir.getAbsolutePath() + "' is not an directory.");
		}
		File[] files = dir.listFiles();
		for (int i = 0; i < files.length; i++) {
			if (files[i].isFile()) {
				removeFile(files[i]);
			} else {
				removeDirectory(files[i]);
			}
		}
		dir.delete();
	}

}
